/**
 * 文件名称: file_transfer.c
 * 摘    要: 与文件传输相关的操作函数源文件
 * 来    源: huenrong
 *
 * 当前版本: 1.0 
 * 作    者: huenrong
 * 完成日期: 2019-07-28
 **/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <stdlib.h>

#include "file_transfer.h"

unsigned char g_frame_number = 0;       // 上传数据中的帧序号(应答时也回应相同帧序号)
struct file_info g_file_info = {0};     // 记录上传文件信息


// CRC查表值
const unsigned char auchCRCHi[]=  
{
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,  
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,  
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,  
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,  
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,  
    0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,  
    0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,  
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,  
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,  
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,  
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,  
    0x40  
};  
  
const unsigned char auchCRCLo[] =  
{
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,  
    0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,  
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,  
    0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,  
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,  
    0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,  
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,  
    0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,  
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,  
    0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,  
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,  
    0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,  
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,  
    0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,  
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,  
    0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,  
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,  
    0x40  
};

/************************************************************************
函数名称: calc_crc16
函数功能: 计算N字节的Crc校验
函数参数: user_data: 需要计算CRC的数据
          user_data_len: 需要计算CRC的数据长度
函数返回: 计算得到的CRC
************************************************************************/
static unsigned short calc_crc16(const unsigned char *user_data, unsigned long user_data_len)  
{  
    unsigned char uchCRCHi = 0xFF;
    unsigned char uchCRCLo = 0xFF;
    unsigned short uindex = 0;

    while (user_data_len--)
    {
        uindex = uchCRCHi ^ (*user_data++);
        uchCRCHi = uchCRCLo ^ auchCRCHi[uindex];
        uchCRCLo = auchCRCLo[uindex];
    }

    return (uchCRCHi | uchCRCLo<<8);
}

/************************************************************************
函数名称: get_file_size
函数功能: 获取文件大小
函数参数: path: 文件路径
函数返回: 成功: 返回文件大小
          失败: 返回-1
************************************************************************/
unsigned long get_file_size(const char *path)
{
	unsigned long file_size = -1;
	struct stat stat_buff = {0};

	if (stat(path, &stat_buff) < 0)
    {
		return file_size;
	}
    else
    {
		file_size = stat_buff.st_size;
	}

	return file_size;
}

/************************************************************************
函数名称: read_file
函数功能: 读取文件内容
函数参数: file_path: 文件路径
          position: 读取内容的起始位置
          len: 指定读取的长度
          read_buf: 读取到的文件内容
函数返回: 成功: 返回实际读取到的文件长度
          失败: 返回-1
************************************************************************/
static int read_file(const char *file_path, const unsigned long position, 
            const unsigned long len, char *read_buf)
{
    FILE *fd = NULL;        // 文件描述符
    int ret = -1;

    fd = fopen(file_path, "r");     // 以只读方式打开文件
    if (NULL == fd)
    {
        perror("read_file open");

        return -1;
    }

    ret = fseek(fd, position, SEEK_SET);      // 设置读取内容的起始位置
    if (-1 == ret)
    {
        perror("read_file fseek");

        return -1;
    }

    ret = fread(read_buf, 1, len, fd);      // 读取文件内容
    if (-1 == ret)
    {
        perror("read_file read");

        return -1;
    }

    fclose(fd);      // 关闭文件

    return ret;
}

/************************************************************************
函数名称: display_progress
函数功能: 显示进度条
函数参数: progress: 当前完成的进度值
函数返回: 无
************************************************************************/
static void display_progress(const int progress)
{
    // 将当前行全部清空，用以显示最新的进度条状态
    // \33[2K是清除一行内容，\r是光标回到行首
    printf("\33[2K\r");
    
    int j = 0;
    // 打印进度条上已经完成的部分，用‘+’表示
    for (j=0; j<progress; j++)
    {
        putchar('+');
    }
    
    // 打印进度条上还有多少没有完成的
    for (j=1; j<=100-progress; j++)
    {
        putchar('-');
    }

    fprintf(stdout, "  %3d%%", progress);
    fflush(stdout);
}

/************************************************************************
函数名称: recv_data_parsing
函数功能: 接收数据解析
函数参数: recv_data: 接收到的数据
          recv_data_len: 接收数据长度
          recv_data_type: 接收到的有效数据类型
函数返回: 成功: 返回0(RECV_OK)
          失败: 返回对应错误码(详见enum transfer_response)
************************************************************************/
static int recv_data_parsing(const char *recv_data, const unsigned long recv_data_len, 
                unsigned char *recv_data_type)
{
    unsigned short calc_crc = 0;        // 根据上传数据计算得到的CRC
    unsigned short recv_crc = 0;        // 上传数据中的CRC
    unsigned long file_size = 0;        // 下发数据中获取到的文件大小
    struct file_transfer t_file_transfer = {0};

    memcpy(&t_file_transfer, recv_data, recv_data_len);

    if (SERVER_HEAD != t_file_transfer.frame_head)      // 帧头错误
    {
        printf("t_file_transfer.frame_head = 0x%02X\n", t_file_transfer.frame_head);

        return HEAD_ERR;
    }

    // 获取上传数据中的crc
    recv_crc = recv_data[recv_data_len - 1] << 8 | recv_data[recv_data_len - 2];
    calc_crc = calc_crc16(recv_data, (recv_data_len - 2));      // 计算本地crc
    if (recv_crc != calc_crc)       // CRC校验失败
    {
        return CRC_ERR;
    }

    // 判断数据类型
    switch (t_file_transfer.user_data_type)
    {
        case TRANSFER_FILE_NAME:        // 接收到文件名
        {
            (*recv_data_type) = TRANSFER_FILE_NAME;        // 记录下发有效数据类型

            // 下发的文件名正确
            if (0 == memcmp(g_file_info.file_name, t_file_transfer.user_data, t_file_transfer.user_data_len))
            {
                // printf("file_name ok\n");
            }
            else
            {
                return OTHER_ERR;
            }

            break;
        }

        case TRANSFER_FILE_SIZE:        // 接收到文件大小
        {
            (*recv_data_type) = TRANSFER_FILE_SIZE;        // 记录下发有效数据类型

            // 获取下发数据中的文件大小
            file_size = ((t_file_transfer.user_data[3] << 24) & 0xFF000000) | 
                         ((t_file_transfer.user_data[2] << 16) & 0x00FF0000) | 
                         ((t_file_transfer.user_data[1] << 8) & 0x0000FF00) | 
                         ((t_file_transfer.user_data[0] << 0) & 0x000000FF);
            
            // 下发的文件大小正确
            if (g_file_info.file_size == file_size)
            {
                // printf("file_size ok\n");
            }
            else
            {
                return OTHER_ERR;
            }
            

            break;
        }

        case TRANSFER_FILE_DATA:        // 接收到传输文件内容应答
        {
            (*recv_data_type) = TRANSFER_FILE_DATA;        // 记录下发有效数据类型

            // 传输文件内容应答正确
            if (RECV_OK == t_file_transfer.user_data[0])
            {
                // printf("file_data ok\n");
            }
            else
            {
                return OTHER_ERR;
            }

            break;
        }

        case TRANSFER_FILE_END:         // 接收到的是传输文件结束消息
        {
            (*recv_data_type) = TRANSFER_FILE_END;        // 记录下发有效数据类型

            // 服务器接收文件大小正确
            if (TRANSFER_OK == t_file_transfer.user_data[0])
            {
                return RECV_OK;
            }
            else        // 服务器接收文件大小错误
            {
                return TRANSFER_FAIL;
            }

            break;
        }
        
        default:
            break;
    }
    
    return RECV_OK;
}

/************************************************************************
函数名称: send_data_package
函数功能: 发送数据打包
函数参数: send_data_type: 发送数据类型
          send_user_data: 需要发送的用户数据
          send_user_data_len: 需要发送的用户数据长度
          send_data: 打包完成的数据
          send_data_len: 打包完成的数据长度
函数返回: 无
************************************************************************/
static void send_data_package(const unsigned char send_data_type, 
                const char *send_user_data, const unsigned long send_user_data_len, 
                char *send_data, unsigned long *send_data_len)
{
    unsigned short calc_crc = 0;
    struct file_transfer t_file_transfer = {0};

    t_file_transfer.frame_head = CLIENT_HEAD;       // 帧头
    t_file_transfer.frame_number = g_frame_number++;        // 帧序号
    t_file_transfer.user_data_type = send_data_type;        // 数据类型
    (*send_data_len) = 3;       // 计算帧头、帧序号、数据类型的长度

    // 填充用户数据部分
    t_file_transfer.user_data_len = send_user_data_len;      // 用户数据长度
    if (send_user_data_len > 0)
    {
        memcpy(t_file_transfer.user_data, send_user_data, t_file_transfer.user_data_len);
    }
    (*send_data_len) += (t_file_transfer.user_data_len + 2);

    memcpy(send_data, &t_file_transfer, (*send_data_len));
    
    calc_crc = calc_crc16(send_data, (*send_data_len));     // 计算crc

    send_data[(*send_data_len)++] = (calc_crc & 0x00FF);
    send_data[(*send_data_len)++] = (calc_crc & 0xFF00) >> 8;
}

/************************************************************************
函数名称: send_error_response
函数功能: 发送错误应答数据打包
函数参数: error_type: 错误类型
          send_data: 打包完成的数据
          send_data_len: 打包完成的数据长度
函数返回: 无
************************************************************************/
static void send_error_response_package(const unsigned char error_type, 
                char *send_data, unsigned long *send_data_len)
{
    unsigned short calc_crc = 0;
    struct file_transfer t_file_transfer = {0};

    t_file_transfer.frame_head = SERVER_HEAD;       // 帧头
    t_file_transfer.frame_number = g_frame_number++;        // 帧序号
    t_file_transfer.user_data_type = TRANSFER_FILE_DATA;        // 数据类型
    (*send_data_len) = 3;       // 计算帧头、帧序号、数据类型的长度

    t_file_transfer.user_data_len = 1;      // 计算有效数据长度
    t_file_transfer.user_data[0] = error_type;
    (*send_data_len) += (t_file_transfer.user_data_len + 2);

    memcpy(send_data, &t_file_transfer, (*send_data_len));
    
    calc_crc = calc_crc16(send_data, (*send_data_len));

    send_data[(*send_data_len)++] = (calc_crc & 0x00FF);
    send_data[(*send_data_len)++] = (calc_crc & 0xFF00) >> 8;
}

/************************************************************************
函数名称: file_transfer_client_function
函数功能: 文件传输客户端功能函数
函数参数: client_fd: 客户端套接字
函数返回: 成功: 返回0
          失败: 返回-1/对应错误码
************************************************************************/
int file_transfer_client_function(const int client_fd)
{
    int ret = -1;           // 函数返回值
    long recv_data_len = -1;       // 接收到的数据长度
    unsigned char recv_data_type = 0;       // 接收到的有效数据类型
    unsigned long send_data_len = -1;       // 发送数据长度
    char recv_data[MAX_USER_DATA_LEN + 7] = {0};    // 接收数据
    char send_data[MAX_USER_DATA_LEN + 7] = {0};    // 发送数据
    static unsigned char send_file_name_flag = 0;   // 发送文件名成功标志位
    static unsigned char send_file_size_flag = 0;   // 发送文件大小成功标志位
    static unsigned char send_file_data_flag = 0;   // 发送文件内容结束标志位
    static unsigned long read_data_position = 0;    // 记录读取文件内容位置
    char read_data[MAX_USER_DATA_LEN] = {0};        // 从文件中读取到的内容

    while(1)
    {
        // 还未发送文件名到服务器
        if ((0 == send_file_name_flag) && (0 == send_file_size_flag))
        {
            // 发送文件名数据打包
            memset(send_data, 0, sizeof(send_data));
            send_data_package(TRANSFER_FILE_NAME, g_file_info.file_name, 
                    strlen(g_file_info.file_name), send_data, &send_data_len);
        }
        // 文件名已发送, 但还未发送文件大小到服务器
        else if ((1 == send_file_name_flag) && (0 == send_file_size_flag))
        {
            // 发送文件大小数据打包
            memset(send_data, 0, sizeof(send_data));
            send_data_package(TRANSFER_FILE_SIZE, (const char *)&g_file_info.file_size, 
                    4, send_data, &send_data_len);
        }
        // 发送文件内容
        else if ((1 == send_file_name_flag) && (1 == send_file_size_flag) && 
                 (0 == send_file_data_flag))
        {
            // 读取文件内容到read_data
            memset(read_data, 0, sizeof(read_data));
            ret = read_file(g_file_info.file_name, read_data_position, 
                            MAX_USER_DATA_LEN, read_data);
            if (-1 != ret)
            {
                read_data_position += ret;
            }

            // 发送文件内容数据打包
            memset(send_data, 0, sizeof(send_data));
            send_data_package(TRANSFER_FILE_DATA, read_data, ret, send_data, &send_data_len);
        }
        else        // 发送传输完成消息
        {
            // 发送传输完成消息打包
            memset(send_data, 0, sizeof(send_data));
            send_data_package(TRANSFER_FILE_END, NULL, 0, send_data, &send_data_len);
        }
        
        
    #if 0
        // 打印发送数据
        printf("send_data_len = %ld\n", send_data_len);
        printf("send_data: ");
        for (int i=0; i<send_data_len; i++)
        {
            printf("0x%02X ", send_data[i]);
        }
        printf("\n");
    #endif
        send(client_fd, send_data, send_data_len, 0);        // 向服务器发送数据

        memset(recv_data, 0, sizeof(recv_data));
        recv_data_len = recv(client_fd, recv_data, sizeof(recv_data), 0);        // 接受服务器发送过来的数据
        if (recv_data_len > 0)        // 接收到数据
        {
        #if 0
            // 打印接收数据
            printf("recv_data_len =  %ld\n", recv_data_len);
            printf("recv_data: ");
            for (int i=0; i<recv_data_len; i++)
            {
                printf("0x%02X ", recv_data[i]);
            }
            printf("\n");
        #endif
            ret = recv_data_parsing(recv_data, recv_data_len, &recv_data_type);
            if (RECV_OK == ret)     // 接收数据解析成功
            {
                // 发送文件名成功, 将标志位置1
                if (TRANSFER_FILE_NAME == recv_data_type)
                {
                    printf("send file name ok\n");

                    send_file_name_flag = 1;
                }
                // 发送文件大小成功, 将标志位置1
                else if (TRANSFER_FILE_SIZE == recv_data_type)
                {
                    printf("send file size ok\n");

                    send_file_size_flag = 1;
                }
                // 发送文件内容
                else if (TRANSFER_FILE_DATA == recv_data_type)
                {
                    // 记录完成百分比(保留整数)
                    int progress = ((float)read_data_position / g_file_info.file_size) * 100;
                    display_progress(progress);

                    // 文件内容发送完成, 将标志置1
                    if (g_file_info.file_size == read_data_position)
                    {
                        printf("\nsend file data end\n");

                        send_file_data_flag = 1;
                    }
                }
                // 接收到的是发送文件内容结束标志
                else if (TRANSFER_FILE_END == recv_data_type)
                {
                    // 打印传输成功信息
                    printf("file transfer successful\n");

                    return 0;
                }
            }
            else if (HEAD_ERR)      // 接收数据帧头错误
            {
                printf("file transfer error: recv head error\n");

                return HEAD_ERR;
            }
            else if (CRC_ERR)       // 接收数据CRC错误
            {
                printf("file transfer error: recv crc error\n");

                return CRC_ERR;
            }
            else if (TRANSFER_FAIL)     // 服务器接收文件大小错误
            {
                printf("file transfer fail\n");

                return TRANSFER_FAIL;
            }
            else        // 其它错误
            {
                printf("file transfer error: other error\n");

                return OTHER_ERR;
            }
        }
        else if (recv_data_len == 0)   // socket 正常关闭
        {
            // printf("server socket closed!\n");

            return -1;      // 服务器关闭, 退出函数, 重连服务器
        }
        else if (recv_data_len == -1)  // 非阻塞模式下表示缓冲区空 if(ret == -1)
        {
        }
        else
        {
            // 错误处理
            if (EAGAIN != errno)
            {
                // printf("recv error ret = %d", ret);

                return -1;      // 异常情况, 退出函数, 重连服务器
            }
        }
    }

    // close(client_fd);        // 当前连接断开后，关闭套接字

    return -1;
}

